Skip to content

Conversation

@jorgebaralt
Copy link
Contributor

  • Add better Auth Stripe plugin
  • Manage Stripe subscription from the new subscription modal

@jorgebaralt jorgebaralt merged commit b47fade into main Sep 14, 2025
2 of 3 checks passed
@jorgebaralt jorgebaralt deleted the feat/stripe branch September 14, 2025 20:34
Copy link
Contributor

@rmolinamir rmolinamir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR broke the build CI :

Error: Neither apiKey nor config.authenticator provided

See: stripe/stripe-js#667

Image

Comment on lines +13 to +20
const handleClick = () => {
setCancelUrl(pathname);

// Add a small delay to allow the dropdown to close before navigation
setTimeout(() => {
router.push("/auth/settings");
}, 150);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +60 to +63
quota: 250, // 2.5 USD
},
name: "pro",
priceId: "price_1S2gQfK8Pm3Qm3VvhM5TuvWI",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be configurable/parametized.

@@ -0,0 +1,11 @@
{
"name": "@acme/stripe-listener",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Rename to stripe-tunnel or just stripe in case we add more local development goodies.

"type": "module",
"scripts": {
"clean": "git clean -xdf .cache .turbo node_modules tsconfig.tsbuildinfo",
"dev": "stripe listen --forward-to localhost:3000/api/auth/stripe/webhook",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be configurable (see localtunnel).

Comment on lines +27 to +49
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
setIsLoading(true);

if (!sessionData?.user?.id) {
toast.error("Please sign in to manage your subscription");
return;
}

const result = await authClient.subscription.billingPortal({
locale: "en",
referenceId: sessionData.user.id,
returnUrl: pathname,
});

if (result.error) {
console.error("Billing portal error:", result.error);
toast.error("Failed to open billing portal. Please try again.");
}

// Call the custom onClick handler if provided
onClick?.(event);
setIsLoading(false);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use react-query instead?

Comment on lines +29 to +39
{isOnSubscriptionPage ? (
<div className="flex items-center gap-2">
<CreditCard />
Subscription
</div>
) : (
<Link href="/subscription" className="flex items-center gap-2">
<CreditCard />
Subscription
</Link>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do we need to do this?

Comment on lines +7 to +14
<DropdownMenuItem asChild className="w-full cursor-pointer">
<form action={signOutAction} className="w-full">
<button type="submit" className="flex w-full items-center gap-2">
<LogOut />
Sign out
</button>
</form>
</DropdownMenuItem>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened here?

Comment on lines +17 to +20
// Use effect to prevent hydration mismatch and flashing
useEffect(() => {
setIsOpen(pathname === "/subscription");
}, [pathname]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: I'll check this later

error?: Error;
}

export class BillingErrorBoundary extends Component<Props, State> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class?

collapsible="offcanvas"
variant="floating"
className="sticky top-0 h-svh"
className="sticky top-0 z-10 h-svh"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably unnecessary 🤔

Comment on lines +7 to +9
"module": "ES2022",
"moduleResolution": "bundler",
"skipLibCheck": true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? Not ESM friendly I think (which is not ideal).

Comment on lines +12 to +23
id: text("id").primaryKey(),
plan: text("plan").notNull(),
referenceId: text("reference_id").notNull(),
stripeCustomerId: text("stripe_customer_id"),
stripeSubscriptionId: text("stripe_subscription_id"),
status: text("status").default("incomplete"),
periodStart: timestamp("period_start"),
periodEnd: timestamp("period_end"),
trialStart: timestamp("trial_start"),
trialEnd: timestamp("trial_end"),
cancelAtPeriodEnd: boolean("cancel_at_period_end"),
seats: integer("seats"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I like keeping the underscores to avoid accidental bugs (skill issue lol).

} from "drizzle-orm/pg-core";

// Create the subscription table
export const subscription = pgTable("subscription", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing user_id foreign key.

updatedAt: timestamp("updated_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
stripeCustomerId: text("stripe_customer_id"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this, rely on foreign key on the subscription table instead.

id: text("id").primaryKey(),
plan: text("plan").notNull(),
referenceId: text("reference_id").notNull(),
stripeCustomerId: text("stripe_customer_id"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs to be unique.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants